use super::user_service::UserService;
use crate::{
    errors::cons_error,
    mdb::{MClient, MDB},
    models::wechat_interactive::WechatSettingModel,
    AppError, AppResult, Config,
};
use actix_http::StatusCode;
use awc::Client;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{fmt::Display, sync::Arc};
use wither::{
    bson::{doc, DateTime},
    mongodb::options::{FindOneAndUpdateOptions, ReturnDocument},
    Model,
};
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct WechatService {
    config: Arc<Config>,
    user_service: Arc<UserService>,
    mdb: MDB,
    m_client: MClient,
}

impl WechatService {
    pub fn new(
        config: Arc<Config>,
        user_service: Arc<UserService>,
        mdb: MDB,
        m_client: MClient,
    ) -> Self {
        Self {
            config,
            user_service,
            mdb,
            m_client,
        }
    }
    /// ## 测试请求
    /// TODO: 移除此方法
    pub async fn test_req(&self) -> AppResult<Value> {
        let client = Client::default();
        let mut res = client
            .get("https://shimmer.icu/api/club")
            .send()
            .await
            .map_err(actix_web::error::ErrorInternalServerError)?;
        if res.status() != StatusCode::OK {
            return Err(AppError::Status(res.status(), "req field".to_owned()));
        }
        let body = res
            .json()
            .await
            .map_err(actix_web::error::ErrorInternalServerError)?;
        Ok(body)
    }
    /// ## 尝试初始化Wechat全局配置
    /// ### Q: 为什么不加入缓存呢?
    /// A: 因为这里的相关token是对整个应用全局的，需要有一个中心话的token获取机制。如果采用多实例部署的话加入缓存就会出现token频繁失效的问题
    pub async fn find_or_init_global_config(&self) -> AppResult<WechatSettingModel> {
        let coll = WechatSettingModel::collection(&*self.mdb);
        let ops = FindOneAndUpdateOptions::builder()
            .upsert(true)
            .return_document(ReturnDocument::After)
            .build();
        let res = coll
            .find_one_and_update(
                doc! {},
                doc! {
                    "$setOnInsert":WechatSettingModel::default().document_from_instance()?
                },
                ops,
            )
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_MAYBE_BROKEN))?;
        WechatSettingModel::instance_from_document(res).map_err(AppError::from)
    }
    pub async fn update_access_token(&self) -> AppResult<WechatSettingModel> {
        #[derive(Debug, Serialize, Deserialize)]
        struct AccessTokenRes {
            access_token: String,
            expires_in: i64,
        }
        if let (Some(appid), Some(secret)) =
            (&self.config.wechat.app_id, &self.config.wechat.app_secret)
        {
            let client = Client::default();

            let res: Value = client
                .get(format!(
                    "{}?grant_type=client_credential&appid={}&secret={}",
                    wechat_apis::GET_ACCESS_TOKEN,
                    appid,
                    secret
                ))
                .send()
                .await
                .map_err(AppError::other_error)?
                .json()
                .await
                .map_err(AppError::other_error)?;
            if is_wechat_error_req(&res) {
                let e: WechatRequestError = serde_json::from_value(res)?;
                log::debug!("update_access_token请求失败 error: {}", e);
                return Err(AppError::other_error(e));
            }
            let res: AccessTokenRes = serde_json::from_value(res)?;
            log::debug!("update_access_token请求成功 res: {:?}", res);
            let coll = WechatSettingModel::collection(&*self.mdb);
            let now = chrono::Utc::now() + chrono::Duration::seconds(res.expires_in);
            coll.find_one_and_update(
                doc! {},
                doc! {
                    "$set":{
                        "access_token":res.access_token,
                        "access_expires_time":DateTime::from_chrono(now)
                    }
                },
                FindOneAndUpdateOptions::builder()
                    .return_document(ReturnDocument::After)
                    .build(),
            )
            .await?
            .ok_or(AppError::ConsError(cons_error::MSG_MDB_MAYBE_BROKEN))
            .map(WechatSettingModel::instance_from_document)?
            .map_err(AppError::other_error)
        } else {
            Err(AppError::ConsError(cons_error::MSG_WECHAT_NOT_CONFIG))
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct WechatRequestError {
    #[serde(rename = "errcode")]
    pub err_code: i64,
    #[serde(rename = "errmsg", default = "Default::default")]
    pub err_msg: String,
}
impl Display for WechatRequestError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!(
            "request for wechat error. errcode:{}. errmsg:{}.",
            self.err_code, self.err_msg
        ))
    }
}
/// ## 检查是否为微信错误返回体
fn is_wechat_error_req(val: &Value) -> bool {
    val["errcode"].is_number()
}
mod wechat_apis {
    macro_rules! define_api {
        ($n:ident,$v:expr) => {
            pub static $n: &str = $v;
        };
    }
    define_api!(GET_ACCESS_TOKEN, "https://api.weixin.qq.com/cgi-bin/token");
}
